#include "Fractal.h"
#include "RandomNumberGenerator.h"
#include <assert.h>

Fractal::Fractal()
{
	m_Graph = Graph();
}
Fractal::~Fractal()
{}
float GetRand(float var)
{
	if(var<0.0005)
	{
		return 0;
	}
	int i = var*2*1000;
	int r = MY_RAND(i);
	float res = r-(var*1000);
	return res/1000;
}
bool SquareDiamond(std::vector<std::vector<float>>& heightPoints, D3DXVECTOR2 corners[4], float var)
{

	//check if smallest possible
	if(corners[0].x+1 >= corners[1].x)
	{
		//bottom corners are adjacent, this quadrent complete
		return true;
	}

	float variance=0;
	if (var>0)
	{
		variance= var/2;
		//variance = sqrt(var);
	}

	//find centre from 4 points
	int x= ((corners[3].x-corners[0].x)/2)+corners[0].x;
	int y= ((corners[3].y-corners[0].y)/2)+corners[0].y;
	D3DXVECTOR2 centre(x,y);

	float a = heightPoints[corners[0].x][corners[0].y];
	float b = heightPoints[corners[1].x][corners[1].y];
	float c = heightPoints[corners[2].x][corners[2].y];
	float d = heightPoints[corners[3].x][corners[3].y];

	float mean = (a+b+c+d)/4;
	heightPoints[x][y]=mean+GetRand(variance);

	//find diamond points
	D3DXVECTOR2 diamondCorners[4];

	
	diamondCorners[0]=D3DXVECTOR2(((corners[1].x-corners[0].x)/2)+corners[0].x,corners[0].y);
	diamondCorners[3]=D3DXVECTOR2(((corners[1].x-corners[0].x)/2)+corners[0].x,corners[3].y);

	diamondCorners[2]=D3DXVECTOR2(corners[0].x,((corners[2].y-corners[0].y)/2)+corners[0].y);
	diamondCorners[1]=D3DXVECTOR2(corners[3].x,((corners[2].y-corners[0].y)/2)+corners[0].y);

	//find diamond point heights

	heightPoints[diamondCorners[0].x][diamondCorners[0].y]= ((heightPoints[corners[0].x][corners[0].y]+heightPoints[corners[1].x][corners[1].y])/2)+GetRand(variance);
	heightPoints[diamondCorners[3].x][diamondCorners[3].y]= ((heightPoints[corners[2].x][corners[2].y]+heightPoints[corners[3].x][corners[3].y])/2)+GetRand(variance);

	heightPoints[diamondCorners[1].x][diamondCorners[1].y]= ((heightPoints[corners[0].x][corners[0].y]+heightPoints[corners[2].x][corners[2].y])/2)+GetRand(variance);
	heightPoints[diamondCorners[2].x][diamondCorners[2].y]= ((heightPoints[corners[1].x][corners[1].y]+heightPoints[corners[3].x][corners[3].y])/2)+GetRand(variance);


	//setup next 4 squares
	D3DXVECTOR2 v1[4]={diamondCorners[2],centre,corners[2],diamondCorners[3]};
	D3DXVECTOR2 v2[4]={centre,diamondCorners[1],diamondCorners[3],corners[3]};
	D3DXVECTOR2 v3[4]={corners[0],diamondCorners[0],diamondCorners[2],centre};
	D3DXVECTOR2 v4[4]={diamondCorners[0],corners[1],centre,diamondCorners[1]};
	
	//recurse new squares into this function

	bool Res1 = SquareDiamond(heightPoints,v1,variance);
	bool Res2 = SquareDiamond(heightPoints,v2,variance);
	bool Res3 = SquareDiamond(heightPoints,v3,variance);
	bool Res4 = SquareDiamond(heightPoints,v4,variance);

	if(!Res1 || !Res2 || !Res3 || !Res4)
	{
		//one of the inner squares failed
		assert(0);
		MessageBox(0,L"Error in fractal generation",L"Error",MB_OK);
		return false;
	}
	return true;

}
void Fractal::Generate(int gridSize,float variance)
{
	if((gridSize-1)%2!=0)
	{
		//Grid must be power of 2! 
		assert(0);
		MessageBox(0,L"Grid must be power of 2",L"Error",MB_OK);
		return;
	}
	m_gridSize=gridSize;

#ifdef RBug

	//full random
	int numOfPoints = gridSize*gridSize;
	
	for(unsigned int i=0;i<numOfPoints;++i)
	{
		m_vPoints.push_back(GetRand(variance));
	}
#endif
#ifndef RBug
	//diamond square
	std::vector <std::vector <float>> Vec2d;
	Vec2d.resize(gridSize);
	
	for(unsigned int y=0;y<Vec2d.size();++y)
	{
		Vec2d[y].resize(gridSize);
		for(unsigned int x=0;x<Vec2d[y].size();++x)
		{
			m_Graph.AddNode(GraphNode(D3DXVECTOR2(x,y)));
			
		}
	}
	
	D3DXVECTOR2 corners[4];
	corners[0]=D3DXVECTOR2(0,0);
	corners[1]=D3DXVECTOR2(gridSize-1,0);
	corners[2]=D3DXVECTOR2(0,gridSize-1);
	corners[3]=D3DXVECTOR2(gridSize-1,gridSize-1);

	//diamond phase
	//generate seed corners
	float a = GetRand(variance);
	float b = GetRand(variance);
	float c = GetRand(variance);
	float d = GetRand(variance);

	Vec2d[corners[0].x][corners[0].y] = a;
	Vec2d[corners[1].x][corners[1].y] = b;
	Vec2d[corners[2].x][corners[2].y] = c;
	Vec2d[corners[3].x][corners[3].y] = d;

	if(!SquareDiamond(Vec2d,corners,variance))
	{
		//PROBLEM!
		assert(0);
	}

	//serialize 2d vector into 1d

	for (unsigned int i=0;i<Vec2d.size();++i)
	{
		for (unsigned int j=0;j<Vec2d[i].size();++j)
		{
			//NOTE! this implementation MAY result in a terrain rotated 90 degrees, needs checking
			m_vPoints.push_back(Vec2d[i][j]);
		}
	}
#endif

	int GN;
	for (unsigned int i=0; i< m_Graph.GetNumNodes();++i)
	{
		D3DXVECTOR2 pos = m_Graph.GetNode(i)->GetPos();
		float height = GetPoint(i);
		
		
		GN = m_Graph.GetNodeID(pos+D3DXVECTOR2(0,1));
		m_Graph.AddEdge(GraphEdge(i,GN,GetPoint(GN)-height));
		GN = m_Graph.GetNodeID(pos+D3DXVECTOR2(1,1));
		m_Graph.AddEdge(GraphEdge(i,GN,GetPoint(GN)-height));
		GN = m_Graph.GetNodeID(pos+D3DXVECTOR2(1,0));
		m_Graph.AddEdge(GraphEdge(i,GN,GetPoint(GN)-height));
		GN = m_Graph.GetNodeID(pos+D3DXVECTOR2(1,-1));
		m_Graph.AddEdge(GraphEdge(i,GN,GetPoint(GN)-height));
		GN = m_Graph.GetNodeID(pos+D3DXVECTOR2(0,-1));
		m_Graph.AddEdge(GraphEdge(i,GN,GetPoint(GN)-height));
		GN = m_Graph.GetNodeID(pos+D3DXVECTOR2(-1,-1));
		m_Graph.AddEdge(GraphEdge(i,GN,GetPoint(GN)-height));
		GN = m_Graph.GetNodeID(pos+D3DXVECTOR2(-1,0));
		m_Graph.AddEdge(GraphEdge(i,GN,GetPoint(GN)-height));
		GN = m_Graph.GetNodeID(pos+D3DXVECTOR2(-1,1));
		m_Graph.AddEdge(GraphEdge(i,GN,GetPoint(GN)-height));
		
	}
}

int Fractal::NumOfNodes()
{
	return m_vPoints.size();
}
float Fractal::GetPoint(int i)
{
	if (i>-1)
	{
		return m_vPoints[i];
	}
	return 0;
}
float Fractal::GetPoint(int x, int y)
{
	int point = (y*m_gridSize)+x;
	return GetPoint(point);
}
float Fractal::GetPoint(D3DXVECTOR2 v)
{
	return GetPoint(v.x, v.y);
}
D3DXVECTOR3 Fractal::GetPos(int i)
{
	int x = i%(m_gridSize);
	int y = GetPoint(i);
	int z = (i-x)/(m_gridSize);
	return D3DXVECTOR3(x,y,z);
}
int Fractal::GetGridSize()
{
	return m_gridSize;
}